use std::fs;

use crate::Puzzle;

#[derive(Clone, Debug, PartialEq, Copy)]
struct BoardCell {
    value: u32,
    drawn: bool,
}

impl From<u32> for BoardCell {
    fn from(value: u32) -> Self {
        BoardCell {
            value,
            drawn: false,
        }
    }
}

type Board = [[BoardCell; 5]; 5];

#[derive(Clone, Debug, PartialEq)]
pub struct GiantSquid {
    numbers_to_draw: Vec<u32>,
    boards: Vec<Board>,
}

impl GiantSquid {
    fn from(lines: &str) -> Self {
        let mut lines: Vec<&str> = lines.lines().map(|line| line.trim()).collect();

        if let Some(line) = lines.first()
            && line.is_empty()
        {
            lines.remove(0);
        }
        if let Some(line) = lines.last()
            && line.is_empty()
        {
            lines.pop();
        }

        let numbers = lines
            .remove(0)
            .split(',')
            .map(|value| value.parse::<u32>().unwrap())
            .collect::<Vec<u32>>();

        let boards = lines
            .chunks(6)
            .map(|lines| {
                let mut board = [[BoardCell {
                    value: 0,
                    drawn: false,
                }; 5]; 5];

                for (i, line) in lines[1..=5].iter().enumerate() {
                    for (j, value) in line.split_whitespace().enumerate() {
                        board[i][j].value = value.parse::<u32>().unwrap();
                    }
                }

                board
            })
            .collect::<Vec<Board>>();

        Self {
            numbers_to_draw: numbers,
            boards,
        }
    }

    fn has_board_won(board: &Board) -> bool {
        for row in board {
            if row.iter().all(|BoardCell { drawn, .. }| *drawn) {
                return true;
            }
        }

        for column_index in 0..5 {
            let mut drawn_count = 0;

            for row_index in 0..5 {
                if board[row_index][column_index].drawn {
                    drawn_count += 1;
                }
            }

            if drawn_count == 5 {
                return true;
            }
        }

        false
    }
}

impl Puzzle for GiantSquid {
    type Answer = u32;

    fn new() -> Self {
        let input = fs::read_to_string("input/day04.md").unwrap();

        Self::from(&input)
    }

    fn solve_part1(&self) -> Self::Answer {
        let mut boards = self.boards.clone();

        for number in &self.numbers_to_draw {
            for board in boards.iter_mut() {
                // mark
                'travel_board: for row in board.iter_mut() {
                    for cell in row.iter_mut() {
                        if cell.value == *number {
                            cell.drawn = true;
                            break 'travel_board;
                        }
                    }
                }

                // check
                if Self::has_board_won(board) {
                    return number
                        * board
                            .iter()
                            .flatten()
                            .filter(|cell| !cell.drawn)
                            .map(|BoardCell { value, .. }| value)
                            .sum::<u32>();
                }
            }
        }

        0
    }

    fn solve_part2(&self) -> Self::Answer {
        let mut boards = self.boards.clone();
        let mut won_board_results: Vec<u32> = vec![];

        for number in &self.numbers_to_draw {
            let mut won_board_indexes: Vec<usize> = vec![];

            for (board_index, board) in boards.iter_mut().enumerate() {
                // mark
                'travel_board: for row in board.iter_mut() {
                    for cell in row.iter_mut() {
                        if cell.value == *number {
                            cell.drawn = true;
                            break 'travel_board;
                        }
                    }
                }

                // check
                if Self::has_board_won(board) {
                    won_board_results.push(
                        number
                            * board
                                .iter()
                                .flatten()
                                .filter(|cell| !cell.drawn)
                                .map(|BoardCell { value, .. }| value)
                                .sum::<u32>(),
                    );
                    won_board_indexes.push(board_index);
                }
            }

            boards = boards
                .iter()
                .enumerate()
                .filter_map(|(index, board)| {
                    if !won_board_indexes.contains(&index) {
                        Some(*board)
                    } else {
                        None
                    }
                })
                .collect()
        }

        *won_board_results.last().unwrap()
    }
}

#[cfg(test)]
mod test {
    use super::*;

    fn create_squid() -> GiantSquid {
        GiantSquid::from(
            "
            7,4,9,5,11,17,23,2,0,14,21,24,10,16,13,6,15,25,12,22,18,20,8,19,3,26,1

            22 13 17 11  0
            8  2 23  4 24
            21  9 14 16  7
            6 10  3 18  5
            1 12 20 15 19

            3 15  0  2 22
            9 18 13 17  5
            19  8  7 25 23
            20 11 10 24  4
            14 21 16 12  6

            14 21 17 24  4
            10 16 15  9 19
            18  8 23 26 20
            22 11 13  6  5
            2  0 12  3  7
            ",
        )
    }

    #[test]
    fn new() {
        let squid = create_squid();

        assert_eq!(
            squid,
            GiantSquid {
                numbers_to_draw: vec![
                    7, 4, 9, 5, 11, 17, 23, 2, 0, 14, 21, 24, 10, 16, 13, 6, 15, 25, 12, 22, 18,
                    20, 8, 19, 3, 26, 1
                ],
                boards: [
                    [
                        [22, 13, 17, 11, 0],
                        [8, 2, 23, 4, 24],
                        [21, 9, 14, 16, 7],
                        [6, 10, 3, 18, 5],
                        [1, 12, 20, 15, 19]
                    ],
                    [
                        [3, 15, 0, 2, 22],
                        [9, 18, 13, 17, 5],
                        [19, 8, 7, 25, 23],
                        [20, 11, 10, 24, 4],
                        [14, 21, 16, 12, 6]
                    ],
                    [
                        [14, 21, 17, 24, 4],
                        [10, 16, 15, 9, 19],
                        [18, 8, 23, 26, 20],
                        [22, 11, 13, 6, 5],
                        [2, 0, 12, 3, 7]
                    ]
                ]
                .map(|board| board.map(|row| row.map(|x| x.into())))
                .to_vec()
            }
        );
    }

    #[test]
    fn part1() {
        let squid = create_squid();
        assert_eq!(squid.solve_part1(), 4512);
    }

    #[test]
    fn part2() {
        let squid = create_squid();
        assert_eq!(squid.solve_part2(), 1924);
    }
}
